﻿using System.Collections.Generic;
using System.Linq;
using PeoplesJukebox.Core.Enums;
using PeoplesJukebox.Core.Extensions;
using PeoplesJukebox.Core.Interfaces;
using PeoplesJukebox.Core.Logging;
using PeoplesJukebox.Core.MediaItems;
using PeoplesJukebox.Core.MediaItems.Bases;
using config = PeoplesJukebox.Core.Configuration;

namespace PeoplesJukebox.Core.Catalogues.Bases
{
    /// <summary>
    /// Base class for implementations of music catalogues
    /// </summary>
    public abstract class CatalogueBase : IMediaCatalogue
    {
        #region Private instance fields

        private List<MediaItem> _allItems;
        private List<Artist> _artists;
        private List<Album> _albums;
        private List<Track> _tracks;

        #endregion

        #region Protected properties

        /// <summary>
        /// Returns all known media items
        /// </summary>
        protected List<MediaItem> AllItems
        {
            get
            {
                return this.LazyLoad<List<MediaItem>>(ref this._allItems,
                   () => new List<MediaItem>());
            }
        }

        #endregion

        #region Public properties

        /// <summary>
        /// Returns all known artists
        /// </summary>
        public List<Artist> Artists
        {
            get
            {
                return this.LazyLoad<List<Artist>>(ref this._artists,
                    () => new List<Artist>(GetItems<Artist>(MediaItemType.Artist)));
            }
        }

        /// <summary>
        /// Returns all known albums
        /// </summary>
        public List<Album> Albums
        {
            get
            {
                return this.LazyLoad<List<Album>>(ref this._albums,
                    () => new List<Album>(GetItems<Album>(MediaItemType.Album)));
            }
        }

        /// <summary>
        /// Returns all known tracks
        /// </summary>
        public List<Track> Tracks
        {
            get
            {
                return this.LazyLoad<List<Track>>(ref this._tracks,
                    () => new List<Track>(GetItems<Track>(MediaItemType.Track)));
            }
        }

        #endregion

        #region Constructors

        /// <summary>
        /// Default constructor
        /// </summary>
        public CatalogueBase()
        {
            Log.Info("{0}: LoadItems started", this.GetType().Name);
            this.LoadItems();
            Log.Info("LoadItems completed. Loaded {0} tracks", this.Tracks.Count);
        }

        #endregion

        #region Abstract methods

        /// <summary>
        /// Method to return a random track ID
        /// </summary>
        /// <returns>Track ID</returns>
        protected abstract int GetRandomTrackId();

        /// <summary>
        /// Method to load media items from the catalogue source
        /// </summary>
        protected abstract void LoadItems();

        #endregion

        #region Private instance methods

        private void CleanSearchTerm(string searchTerm)
        {
            //TODO: only deal with characters and space:
            searchTerm = searchTerm.ToUpper();
        }

        #endregion

        #region Protected instance methods

        /// <summary>
        /// Adds a media item to the collection
        /// </summary>
        /// <param name="id">Item ID</param>
        /// <param name="parentId">Parent item's ID</param>
        /// <param name="name">Item name</param>
        /// <param name="albumName">Album name</param>
        /// <param name="artistName">Artist name</param>
        /// <param name="uri">URI for the item source</param>
        /// <param name="mediaItemType">Type of item</param>
        protected void AddItem(int id, int parentId, string name, string albumName, string artistName, string uri, MediaItemType mediaItemType)
        {
            MediaItem item = this.GetMediaItem(mediaItemType);
            item.Id = id;
            item.ParentId = parentId;
            item.Name = name;
            item.AlbumName = albumName;
            item.ArtistName = artistName;
            item.SearchName = name.Trim().ToUpper();
            item.Uri = uri;
            this.AllItems.Add(item);
        }

        /// <summary>
        /// Returns a new instance of a media item
        /// </summary>
        /// <param name="mediaItemType">Item type</param>
        /// <returns>Media item</returns>
        protected MediaItem GetMediaItem(MediaItemType mediaItemType)
        {
            MediaItem item = null;
            switch (mediaItemType)
            {
                case MediaItemType.Album:
                    item = new Album();
                    break;
                case MediaItemType.Artist:
                    item = new Artist();
                    break;
                case MediaItemType.Track:
                    item = new Track();
                    break;
            }
            return item;
        }

        /// <summary>
        /// Returns all items of the given type
        /// </summary>
        /// <typeparam name="T">Type of media item</typeparam>
        /// <param name="itemType">Item type</param>
        /// <returns>Collection of items</returns>
        protected List<T> GetItems<T>(MediaItemType itemType)
            where T : MediaItem
        {
            return AllItems.FindAll(i => i.ItemType == itemType)
                           .ConvertAll<T>(i => (T)i);
        }

        /// <summary>
        /// Returns an item from a collection
        /// </summary>
        /// <typeparam name="T">Type of media item</typeparam>
        /// <param name="id">Item ID</param>
        /// <param name="items">List of items</param>
        /// <returns>Item</returns>
        protected T GetItem<T>(int id, List<T> items)
            where T : MediaItem
        {
            return items.Find(i => i.Id == id);
        }

        /// <summary>
        /// Returns the child items for an item
        /// </summary>
        /// <typeparam name="T">Type of media item</typeparam>
        /// <param name="parentId">Parent item ID</param>
        /// <param name="items">List of items</param>
        /// <returns>Child items</returns>
        protected List<T> GetItemsByParent<T>(int parentId, List<T> items)
            where T : MediaItem
        {
            return items.FindAll(i => i.ParentId == parentId)
                           .ConvertAll<T>(i => (T)i);
        }

        /// <summary>
        /// Searches media items
        /// </summary>
        /// <typeparam name="T">Type of item</typeparam>
        /// <param name="searchTerm">Term to search for</param>
        /// <param name="items">Item list to search</param>
        /// <returns>List of matching items</returns>
        protected List<T> Search<T>(string searchTerm, List<T> items)
            where T : MediaItem
        {
            this.CleanSearchTerm(searchTerm);
            return items.FindAll((T item) =>
            {
                if (config.PeoplesJukeboxConfiguration.Current.SearchMode == SearchMode.StartsWith)
                {
                    return item.SearchName.StartsWith(searchTerm);
                }
                else
                {
                    List<string> searchNameParts = new List<string>(item.SearchName.Split(' '));
                    return searchNameParts.Any((string s) =>
                    {
                        return s.StartsWith(searchTerm);
                    });
                }
            });
        }

        #endregion

        #region Public instance methods

        /// <summary>
        /// Searches the catalogue for media items
        /// </summary>
        /// <param name="searchTerm">Search term to match against</param>
        /// <returns>List of matching items</returns>
        public List<MediaItem> Search(string searchTerm)
        {
            return this.Search<MediaItem>(searchTerm.ToUpper(), AllItems);
        }

        /// <summary>
        /// Searches the catalogue for media items
        /// </summary>
        /// <param name="searchTerm">Search term to match against</param>
        /// <param name="itemType">Item type to match</param>
        /// <returns>List of matching items</returns>
        public List<MediaItem> Search(string searchTerm, MediaItemType itemType)
        {
            List<MediaItem> list = null;
            switch (itemType)
            {
                case MediaItemType.Track:
                    list = Tracks.ConvertAll<MediaItem>(i => (MediaItem)i);
                    break;
                case MediaItemType.Album:
                    list = Albums.ConvertAll<MediaItem>(i => (MediaItem)i);
                    break;
                case MediaItemType.Artist:
                    list = Artists.ConvertAll<MediaItem>(i => (MediaItem)i);
                    break;
            }
            return this.Search<MediaItem>(searchTerm.ToUpper(), list);
        }

        /// <summary>
        /// Returns all albums for an artist
        /// </summary>
        /// <param name="artistId">Artist ID</param>
        /// <returns>List oif albums</returns>
        public List<Album> GetAlbumsByArtist(int artistId)
        {
            return (List<Album>)this.GetItemsByParent<Album>(artistId, this.Albums);
        }

        /// <summary>
        /// Returns a random track
        /// </summary>
        /// <returns>Random track</returns>
        public Track GetRandomTrack()
        {
            int trackId = this.GetRandomTrackId();
            return GetTrack(trackId);
        }

        /// <summary>
        /// Returns a track
        /// </summary>
        /// <param name="trackId">Track ID</param>
        /// <returns>Track</returns>
        public Track GetTrack(int trackId)
        {
            return this.GetItem<Track>(trackId, this.Tracks);
        }

        /// <summary>
        /// Returns all tracks for an album
        /// </summary>
        /// <param name="albumId">Album ID</param>
        /// <returns>Listy of album tracks</returns>
        public List<Track> GetTracksByAlbum(int albumId)
        {
            return (List<Track>)this.GetItemsByParent<Track>(albumId, this.Tracks);
        }

        /// <summary>
        /// Returns all tracks for an artist
        /// </summary>
        /// <param name="artistId">Artist ID</param>
        /// <returns>List of artist's tracks</returns>
        public List<Track> GetTracksByArtist(int artistId)
        {
            List<Track> tracks = new List<Track>();
            foreach (Album album in this.GetAlbumsByArtist(artistId))
            {
                tracks.AddRange(album.Tracks);
            }
            return tracks;
        }

        /// <summary>
        /// Returns an artist
        /// </summary>
        /// <param name="artistId">Artist ID</param>
        /// <returns>Artist</returns>
        public Artist GetArtist(int artistId)
        {
            return this.GetItem<Artist>(artistId, this.Artists);
        }

        /// <summary>
        /// Returns al album
        /// </summary>
        /// <param name="albumId">Album ID</param>
        /// <returns>Album</returns>
        public Album GetAlbum(int albumId)
        {
            return this.GetItem<Album>(albumId, this.Albums);
        }

        #endregion

    }
}
